package com.briup.server.log;

import com.briup.common.exception.ServiceException;
import com.briup.common.response.Result;
import com.briup.common.utils.AssertUtil;
import com.briup.common.utils.IpAddressUtil;
import com.briup.common.utils.JwtUtil;
import com.briup.server.domain.bean.Log;
import com.briup.server.security.SecurityContext;
import com.briup.server.service.LogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Map;

/**
 * 日志记录操作的切面类，用于封装日志操作的行为，
 * 在本类中会自动扫描到被标注了@LogAccess注解的Controller层方法，
 * 然后获取到该方法的内容，然后记录操作日志，将操作日志入库
 */
@Aspect
@Component
@RequiredArgsConstructor
public class LogAccessAspect {

    private final ObjectMapper objectMapper;
    private final LogService logService;

    /*
    execution 表达式能指定哪些方法需要被拦截，即关联哪些切入点
     第一个 * 表示返回值类型任意
     com.briup.server.web.controller 表示包名
     * 表示类名任意
     *(..) 表示方法名任意，参数任意
     */
    @Pointcut("execution(* com.briup.server.*.controller.*.*(..)) " +
              "&& @annotation(LogAccess)")
    public void pointcut() {
    }

    /*
    通知方法，用于在切入点方法执行前后添加额外的行为
    这里的通知方法是在切入点方法执行前后执行的，即记录操作日志；
    所以需要使用环绕通知
    */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        try {
            long start = System.currentTimeMillis();
            Log log = initLog(joinPoint);
            // 执行目标方法
            Object result = joinPoint.proceed();
            // 计算执行时长
            log.setTime(System.currentTimeMillis() - start);
            log.setCreateDate(LocalDateTime.now());
            // 如果是用户登录，那么在登录成功后，需要记录登录人的用户名
            if (log.getUrl().contains("/login")) {
                Result<Map<String, Object>> resultObj = (Result<Map<String, Object>>) result;
                String token = (String) resultObj.getData().get("token");
                log.setUsername(JwtUtil.getInfo(token, "username", String.class));
            } else {
                log.setUsername(SecurityContext.currentUser().getUsername());
            }
            // 记录操作日志
            logService.saveOrUpdate(log);
            return result;
        } catch (Throwable throwable) {
            if (throwable instanceof ServiceException serviceException) {
                throw serviceException;
            }
            throw new RuntimeException(throwable);
        }
    }

    private Log initLog(ProceedingJoinPoint joinPoint) throws JsonProcessingException {
        // 初始化日志对象
        Log log = new Log();
        // 操作描述，获取注解中的值
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogAccess logAccess = method.getAnnotation(LogAccess.class);
        log.setOperation(logAccess.value());
        Object[] args = joinPoint.getArgs();
        // 设置请求参数，使用json格式保持参数的完整性
        log.setParams(objectMapper.writeValueAsString(args));
        // 获取HTTP请求对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
        AssertUtil.notNull(requestAttributes, "请求属性为空");
        HttpServletRequest request = requestAttributes.getRequest();
        // 获取请求URL
        log.setUrl(request.getRequestURL().toString());
        // 获取请求方式
        log.setMethod(request.getMethod());
        // 获取IP地址
        log.setIp(IpAddressUtil.getClientIpAddress(request));
        return log;
    }
}
